SpringBoot 整合 WebClient
WebClient 是什么
WebClient 是 Spring5 引入的异步非阻塞的响应式的网络框架(用于代替上面的 RestTemplate)
他的执行流程是先创建个 webclient.create()
实例,之后调用 get()
, post()
等调用方式, uri()
指定路径, retrieve()
用来发起请求并获得响应,bodyToMono(String.class)
用来指定请求结果需要处理为 String,并包装为 Reactor 的 Mono 对象
// 直接创建
WebClient webClient = WebClient.create();
// 链式调用
Mono<String> mono = webClient.get().uri("https://www.baidu.com").retrieve().bodyToMono(String.class);
//
mono.subscribe(System.out::println);
// 使用建造模式创建
WebClient webClient1 = WebClient.builder()
.baseUrl("https://api.github.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
.defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
.build();
GET、POST 请求
GET 请求
Mono<String> resp = WebClient.create()
.method(HttpMethod.GET)
.uri("http://www.baidu.com")
.cookie("token","xxxx")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.retrieve().bodyToMono(String.class);
POST 请求
可以使用 BodyInserters 类提供的各种工厂方法来构造 BodyInserter 对象并将其传递给 body 方法。BodyInserters 类包含从Object、Publisher、Resource、FormData、MultipartData 等创建 BodyInserter 的方法。
@Test
public void testFormParam(){
MultiValueMap<String, String> formData = new LinkedMultiValueMap();
formData.add("name1","value1");
formData.add("name2","value2");
Mono<String> resp = WebClient.create().post()
.uri("http://www.w3school.com.cn/test/demo_form.asp")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.retrieve().bodyToMono(String.class);
System.out.print("result:" + resp.block());
}
发送 JSON
static class Book {
String name;
String title;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
@Test
public void testPostJson(){
Book book = new Book();
book.setName("name");
book.setTitle("this is title");
Mono<String> resp = WebClient.create().post()
.uri("http://localhost:8080/demo/json")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(book),Book.class)
.retrieve().bodyToMono(String.class);
LOGGER.info("result:{}",resp.block());
}
异常处理
可以使用 onStatus 根据 status code 进行异常适配
可以使用 doOnError 异常适配
可以使用 onErrorReturn 返回默认值
@Test
public void testFormParam4xx(){
WebClient webClient = WebClient.builder()
.baseUrl("https://api.github.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
.defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
.build();
WebClient.ResponseSpec responseSpec = webClient.method(HttpMethod.GET)
.uri("/user/repos?sort={sortField}&direction={sortDirection}",
"updated", "desc")
.retrieve();
Mono<String> mono = responseSpec
.onStatus(e -> e.is4xxClientError(),resp -> {
log.error("error:{},msg:{}",resp.statusCode().value(),resp.statusCode().getReasonPhrase());
return Mono.error(new RuntimeException(resp.statusCode().value() + " : " + resp.statusCode().getReasonPhrase()));
})
.bodyToMono(String.class)
.doOnError(WebClientResponseException.class, err -> {
log.info("ERROR status:{},msg:{}",err.getRawStatusCode(),err.getResponseBodyAsString());
throw new RuntimeException(err.getMessage());
})
.onErrorReturn("fallback");
String result = mono.block();
System.out.print(result);
}
响应结果
retrieve 方法是直接获取响应 body,但是,如果需要响应的 头信息、Cookie 等,可以使用 exchange 方法,该方法可以访问整个 ClientResponse。由于响应的得到是异步的,所以都可以调用 block 方法来阻塞当前程序,等待获得响应的结果。
String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.create(baseUrl);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("username", "u123");
map.add("password", "p123");
Mono<ClientResponse> mono = webClient.post().uri("login").syncBody(map).exchange();
ClientResponse response = mono.block();
if (response.statusCode() == HttpStatus.OK) {
Mono<Result> resultMono = response.bodyToMono(Result.class);
resultMono.subscribe(result -> {
if (result.isSuccess()) {
ResponseCookie sidCookie = response.cookies().getFirst("sid");
Flux<User> userFlux = webClient.get().uri("users").cookie(sidCookie.getName(), sidCookie.getValue()).retrieve().bodyToFlux(User.class);
userFlux.subscribe(System.out::println);
}
});
}
TODO: 待更新...